package main

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

const JWT_KEY_USER string = "USER_OBJ"
const JWT_KEY_TOKEN string = "USER_TOKEN"
const JWT_KEY_EMAIL string = "USER_EMAIL"
const GAVATAR_URL_PREFIX string = "http://gravatar.com/avatar/"
const GAVATAR_PROXY_URL string = "/gavatar/"

type DeviceInfo struct {
	Devdbid    int    `json:"devdbid"`
	DevUUID    string `json:"devUUID"`
	Imei       string `json:"imei"`
	PeerAddr   string `json: "peeraddr"`
	CreateTime string `json: "createtime"`
	Stat       string `json:"stat"`
}

//gin middleware
func jwt_middleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		//before.
		tokenHeader, ok := c.Request.Header["Authorization"]
		if !ok {
			c.JSON(http.StatusUnauthorized, gin.H{"msg": "Authorization not found."})
			c.Abort()
			return
		}
		if len(tokenHeader) == 0 {
			c.JSON(http.StatusUnauthorized, gin.H{"msg": "Authorization empty."})
			c.Abort()
			return
		}
		tokenstr := tokenHeader[0]
		tokenstr = strings.Trim(tokenstr, " \t\r\n")
		tokenRes := strings.Split(tokenstr, " ")
		if len(tokenRes) != 3 {
			c.JSON(http.StatusUnauthorized, gin.H{"msg": "Authorization format error1."})
			c.Abort()
			return
		}

		if tokenRes[0] != "USER" {
			c.JSON(http.StatusUnauthorized, gin.H{"msg": "Authorization format error2."})
			c.Abort()
			return
		}

		email, token := tokenRes[1], tokenRes[2]

		user, err := QueryUserFromEmailToken(email, token)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"msg": "cannot found valid authorization token."})
			c.Abort()
			return
		}

		c.Set(JWT_KEY_EMAIL, email)
		c.Set(JWT_KEY_TOKEN, token)
		c.Set(JWT_KEY_USER, user)
		//processing
		c.Next()

		//after
	}
}

/*
api_v1.POST("/login/", NullHandler)
api_v1.POST("/logout/", NullHandler)
api_v1.POST("/register/", NullHandler)
api_v1.GET("/general/", NullHandler)
api_v1.GET("/devices/", NullHandler)
api_v1.POST("/devices/", NullHandler)
api_v1.DELETE("/devices/:devId/", NullHandler)
api_v1.GET("/sessions/", NullHandler)
*/

type LoginResponse struct {
	AccessToken   string `json:"access_token"`
	RefreshToken  string `json:"refresh_token"`
	ExpireAccess  uint   `json:"expire_access"`
	ExpireRefresh uint   `json:"expire_refresh"`
	UserId        uint   `json:"userid"`
	Email         string `json:"email"`
	Avatar        string `json:"avatar"`
	CreatedTime   string `json:"create_time"`
	LastLogin     string `json:"last_login"`
	Timestamp     uint   `json:"timestamp"`
}

func FindLoginUser(email string, password string) (user User, err error) {
	pwd := sha512Str(password)

	var users []User
	db := gldb.Where("email = ? AND password = ? AND stat='VALID'", email, pwd)
	if db == nil || db.Error != nil {
		logger.Warningf("query1 user login failed. %s, %s", email, password)
		err = fmt.Errorf("findLoginUser query failed.")
		return
	}
	query := db.Find(&users)
	if query.Error != nil {
		logger.Warningf("query user login failed. %s, %s, %s", email, password, query.Error)
		err = query.Error
		return
	}
	if len(users) == 0 {
		err = fmt.Errorf("cannto found user %s", email)
		return
	}
	if len(users) > 1 {
		logger.Errorf("dup user %s", email)
	}
	user = users[0]
	return
}

func LoginHandler(c *gin.Context) {
	var jsonreq struct {
		Email    string `json:"email" binding:"required"`
		Password string `json:"password" binding:"required"`
	}
	err := c.BindJSON(&jsonreq)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "bad request."})
		return
	}

	user, err := FindLoginUser(jsonreq.Email, jsonreq.Password)
	if err != nil {
		logger.Warningf("Login request failed. %s", err)
		c.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	authtoken := UserAuthToken{
		User:   user,
		Expire: 99999,
		Token:  NewAuthTokenUUID(),
		Stat:   "VALID",
	}
	gldb.NewRecord(authtoken)
	if gldb.Create(&authtoken).Error != nil {
		logger.Warningf("create auth token failed.")
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	avatar := user.Avatar
	if avatar == "" {
		avatar = GAVATAR_PROXY_URL + md5str(strings.ToLower(user.Email)) + "/"
	}
	rsp := LoginResponse{
		AccessToken:   authtoken.Token,
		RefreshToken:  authtoken.Token,
		ExpireAccess:  99999,
		ExpireRefresh: 99999,
		UserId:        user.ID,
		Email:         user.Email,
		Avatar:        avatar,
		CreatedTime:   TimeToString(user.CreatedAt),
		LastLogin:     TimeToString(user.LastLogin),
		Timestamp:     uint(time.Now().Unix()) * 1000,
	}
	c.JSON(http.StatusOK, rsp)
}

func LogoutHandler(c *gin.Context) {
	//https://github.com/ningskyer/gin-doc-cn#middleware-use
	token, ok := c.MustGet("USER_TOKEN").(string)
	if !ok {
		logger.Warningf("logout request, get token failed.")
		c.JSON(http.StatusOK, gin.H{"msg": "OK..."})
		c.Abort()
		return
	}
	var userauthtoken UserAuthToken
	query := gldb.Where("token=? and stat='VALID'", token).First(&userauthtoken)
	if query.Error != nil {
		logger.Warningf("logout request, cannto found token %s", token)
	} else {
		userauthtoken.Stat = "SIGNEDOUT"
		gldb.Save(&userauthtoken)
		gldb.Delete(userauthtoken)
	}

	c.JSON(http.StatusOK, gin.H{"msg": "OK"})
}

func RegisterHandler(c *gin.Context) {
	var jsonreq struct {
		Email    string `json:"email" binding:"required"`
		Password string `json:"password" binding:"required"`
	}
	err := c.BindJSON(&jsonreq)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "bad request."})
		return
	}
	pwd := sha512Str(jsonreq.Password)

	var users []User
	db := gldb.Where("email = ? AND stat='VALID'", jsonreq.Email)
	query := db.Find(&users)
	if query.Error != nil {
		logger.Warningf("register user failed. %s, %s, %s", jsonreq.Email, query.Error)
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}
	if len(users) > 0 {
		logger.Warningf("Find duplicate user %s", jsonreq.Email)
		c.JSON(http.StatusBadRequest, gin.H{"msg": "existed user."})
		return
	}
	user := User{
		Email:     jsonreq.Email,
		Avatar:    "",
		LastLogin: time.Now(),
		Password:  pwd,
		Stat:      "VALID",
	}
	if gldb.Create(&user).Error != nil {
		logger.Warningf("create new user %s failed.", jsonreq.Email)
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	authtoken := UserAuthToken{
		User:   user,
		Expire: 99999,
		Token:  NewAuthTokenUUID(),
		Stat:   "VALID",
	}
	gldb.NewRecord(authtoken)
	if gldb.Create(&authtoken).Error != nil {
		logger.Warningf("create auth token failed.")
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	rsp := LoginResponse{
		AccessToken:   authtoken.Token,
		RefreshToken:  authtoken.Token,
		ExpireAccess:  99999,
		ExpireRefresh: 99999,
		UserId:        user.ID,
		Email:         user.Email,
		Avatar:        user.Avatar,
		CreatedTime:   TimeToString(user.CreatedAt),
		LastLogin:     TimeToString(user.LastLogin),
		Timestamp:     uint(time.Now().Unix()) * 1000,
	}
	c.JSON(http.StatusOK, rsp)
}

type JsonGeneral struct {
	Apps      uint `json:"apps"`
	Devices   uint `json:"devices"`
	TotalUp   uint `json:"totalup"`
	TotalDown uint `json:"totaldown"`
}

func GeneralHandler(c *gin.Context) {
	user, ok := c.MustGet(JWT_KEY_USER).(User)
	if !ok {
		logger.Warningf("cannot found valid user object.")
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	var result JsonGeneral
	query := gldb.Model(&App{}).Where("user_id=? and stat='VALID'", user.ID).Count(&result.Apps)

	query = gldb.Model(&Device{}).Joins("join apps on devices.app_id = apps.id")
	query = query.Where("apps.user_id = ?", user.ID)
	query = query.Where("apps.stat='VALID' and devices.stat='VALID'").Count(&result.Devices)

	query = gldb.Model(&CoapMessage{}).Joins("join devices on devices.id = coap_messages.device_id")
	query = query.Joins("join apps on apps.id = devices.app_id")
	query = query.Where("apps.user_id = ? and coap_messages.stat != 'DELETED' ", user.ID)
	query = query.Where("devices.stat='VALID' and apps.stat = 'VALID'")
	up_query := query.Where("coap_messages.direction=?", MESSAGE_DIRECTION_UP).Count(&result.TotalUp)
	if up_query.Error != nil {
		logger.Warningf("up query failed.")
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}
	down_query := query.Where("coap_messages.direction=?", MESSAGE_DIRECTION_DOWN).Count(&result.TotalDown)
	if down_query.Error != nil {
		logger.Warningf("down query failed.")
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}
	c.JSON(http.StatusOK, result)
}

type JsonDbDevice struct {
	DevDbid uint   `json:"devid"`
	ChipID  string `json:"dev_serial"`
	Name    string `json:"name"`
	Latest  string `json:"latest"`
	AppID   uint   `json:"appid"`
}

func DevicesListHandler(c *gin.Context) {
	result := []JsonDbDevice{}
	var devices []Device
	user := c.MustGet(JWT_KEY_USER).(User)

	query := gldb.Model(&Device{}).Joins("join apps on devices.app_id = apps.id")
	query = query.Where("apps.user_id = ?", user.ID)
	query = query.Where("apps.stat='VALID' and devices.stat='VALID'").Find(&devices)
	if query.Error != nil {
		logger.Warningf("query devices failed.")
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	for _, dev := range devices {
		result = append(result, JsonDbDevice{
			DevDbid: dev.ID,
			ChipID:  dev.ChipId,
			Name:    dev.Name,
			Latest:  TimeToString(dev.Latest),
			AppID:   dev.AppID,
		})
	}

	c.JSON(http.StatusOK, result)
}

func licenseLimit(userid uint) (b bool, err error) {
	//数目判断
	count := 0
	query := gldb.Model(&Device{}).Joins("join apps on devices.app_id = apps.id")
	query = query.Where("apps.user_id = ?", userid)
	query = query.Where("apps.stat='VALID' and devices.stat='VALID'").Count(&count)
	if query.Error != nil {
		err = query.Error
		return
	}
	if count > 50 {
		logger.Warningf("please get license from luogaosong@useease.com, thx.")
		b = true
		return
	}

	query = gldb.Model(&Device{}).Where("stat='VALID'").Count(&count)
	if query.Error != nil {
		err = query.Error
		return
	}

	if count > 200 {
		logger.Warningf("please get license from luogaosong@useease.com,  thx.")
		b = true
		return
	}

	b, err = false, nil
	return
}

func DevicesAddHandler(c *gin.Context) {
	user := c.MustGet(JWT_KEY_USER).(User)

	var jsonreq struct {
		Name   string `json:"name" binding:"required"`
		ChipID string `json:"devserial" binding:"required"`
		AppID  uint   `json:"appid" binding:"required"`
	}
	err := c.BindJSON(&jsonreq)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "bad request."})
		return
	}

	b, err := licenseLimit(user.ID)
	if err != nil {
		logger.Warningf("license query failed.")
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	if b == true {
		c.JSON(http.StatusBadGateway, gin.H{"msg": "need license."})
		c.Abort()
		return
	}
	var dev Device
	query := gldb.Where("chip_id=? and stat='VALID'", jsonreq.ChipID).First(&dev)
	if query.Error == nil {
		logger.Warningf("duplicated device. %s", jsonreq.ChipID)
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	var app App
	query = gldb.Where("id=? and stat='VALID'", jsonreq.AppID).First(&app)
	if query.Error != nil {
		logger.Warningf("cannot found special appid %d", jsonreq.AppID)
		c.AbortWithStatus(http.StatusNotFound)
		return
	}
	if app.UserID != user.ID {
		logger.Warningf("appid %d is not own by user %s", jsonreq.AppID, user.Email)
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}
	/*
		App     App `gorm:"ForeignKey:AppID"`
		AppID   uint
		Name    string `gorm:"size:256"`
		ChipId  string `gorm:"size:64"`
		DevUuid string `gorm:"size:64"`
		Stat    string `gorm:"size:16"`
	*/
	newDevice := Device{
		AppID:   jsonreq.AppID,
		ChipId:  jsonreq.ChipID,
		Name:    jsonreq.Name,
		DevUuid: NewDeviceUUID(),
		Stat:    "VALID",
	}
	gldb.Create(&newDevice)

	/*
		DevDbid uint   `json:"devid"`
		ChipID  uint   `json:"dev_serial"`
		Name    string `json:"name"`
		Latest  string `json:"latest"`
		AppID   uint   `json:"appid"`
	*/
	result := JsonDbDevice{
		DevDbid: newDevice.ID,
		ChipID:  jsonreq.ChipID,
		Name:    jsonreq.Name,
		Latest:  TimeToString(newDevice.CreatedAt),
		AppID:   jsonreq.AppID,
	}
	c.JSON(http.StatusOK, result)
}

type JsonDbSession struct {
	SessionID  int    `json:"sessionid"`
	ItemID     int    `json:"itemid"`
	Direction  string `json:"direction"`
	Message    string `json:"message"`
	CreateTime string `json:"create_time"`
	Stat       string `json:"stat"`
}

func SessionsListHandler(c *gin.Context) {
	result := []JsonDbSession{}
	var messages []CoapMessage

	user := c.MustGet(JWT_KEY_USER).(User)

	query := gldb.Model(&CoapMessage{}).Joins("join devices on devices.id = coap_messages.device_id")
	query = query.Joins("join apps on apps.id = devices.app_id")
	query = query.Where("apps.user_id = ? and coap_messages.stat != 'DELETED' ", user.ID)
	query = query.Where("devices.stat='VALID' and apps.stat = 'VALID'").Find(&messages)
	if query.Error != nil {
		logger.Warningf("query sessions failed.")
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	for _, msg := range messages {
		result = append(result, JsonDbSession{
			SessionID:  TokenInteger(msg.Token),
			ItemID:     int(msg.ID),
			Direction:  MsgDirectionStr(msg.Direction),
			Message:    msg.Data,
			CreateTime: TimeToString(msg.CreatedAt),
			Stat:       MessageStatDisplay(msg.Stat),
		})
	}
	c.JSON(http.StatusOK, result)
}

func GavatarImageFetch(gavatar_userid string) (body []byte, err error) {
	url := GAVATAR_URL_PREFIX + gavatar_userid + "/"
	resp, err := http.Get(url)
	if err != nil {
		logger.Warningf("get gavatar %s failed.", gavatar_userid)
		return
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		logger.Warningf("get gavatar %s from body failed.", gavatar_userid)
		return
	}

	return
}

func UserIcoHandler(c *gin.Context) {
	user := c.MustGet(JWT_KEY_USER).(User)
	body, err := GavatarImageFetch(md5str(strings.ToLower(user.Email)))
	if err != nil {
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	c.Data(http.StatusOK, "image/png", body)
}

func AvatarProxyHandler(c *gin.Context) {
	body, err := GavatarImageFetch(c.Param("avatar"))
	if err != nil {
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	c.Data(http.StatusOK, "image/png", body)
}

type JsonDbApps struct {
	AppID    uint   `json:"appid"`
	Name     string `json:"name"`
	AppKey   string `json:"appkey"`
	CreateAt string `json:"create_time"`
	Note     string `json:"note"`
}

func AppsListHandler(c *gin.Context) {
	user := c.MustGet(JWT_KEY_USER).(User)

	result := []JsonDbApps{}
	var apps []App
	query := gldb.Where("user_id=?", user.ID).Find(&apps)
	if query.Error != nil {
		logger.Warningf("cannot found valid apps.")
		c.AbortWithStatus(http.StatusNotFound)
		return
	}

	for _, app := range apps {
		result = append(result, JsonDbApps{
			AppID:    app.ID,
			Name:     app.Name,
			AppKey:   app.AppKey,
			CreateAt: TimeToString(app.CreatedAt),
			Note:     app.Note,
		})
	}
	c.JSON(http.StatusOK, result)
}

func AddAppHandler(c *gin.Context) {
	user := c.MustGet(JWT_KEY_USER).(User)

	var jsonreq struct {
		Name string `json:"name" binding:"required"`
		Note string `json:"note" binding:"required"`
	}
	err := c.BindJSON(&jsonreq)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "bad request."})
		return
	}

	var app App
	query := gldb.Where("name=? and stat='VALID' and user_id=?", jsonreq.Name, user.ID).First(&app)
	if query.Error == nil {
		logger.Infof("app %s existed, duplicated.", jsonreq.Name)
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}
	newapp := App{
		UserID: user.ID,
		Name:   jsonreq.Name,
		Note:   jsonreq.Note,
		AppKey: NewAppKey(),
		Stat:   "VALID",
	}
	query = gldb.Create(&newapp)
	if query.Error != nil {
		logger.Warningf("create new app failed.")
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	result := JsonDbApps{
		AppID:    newapp.ID,
		Name:     jsonreq.Name,
		AppKey:   newapp.AppKey,
		CreateAt: TimeToString(newapp.CreatedAt),
		Note:     jsonreq.Note,
	}
	c.JSON(http.StatusOK, result)
}

func RemoveAppHandler(c *gin.Context) {
	appid_ := c.Param("appid")
	if appid_ == "" {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "appid error."})
		return
	}
	appid, err := strconv.Atoi(appid_)
	if err != nil {
		logger.Warning("remove app, appid must be digit.")
		c.JSON(http.StatusBadRequest, gin.H{"msg": "error appid"})
		return
	}
	uappid := uint(appid)

	var app App
	query := gldb.Where("id=? and stat='VALID'", uappid).First(&app)
	if query.Error != nil {
		logger.Warningf("cannot found special app. %d", uappid)
		c.AbortWithStatus(http.StatusNotFound)
		return
	}

	app.Stat = "DELETED"
	gldb.Save(&app)
	gldb.Delete(&app)

	c.JSON(http.StatusOK, gin.H{"msg": "OK"})
}

func RemoveDeviceHandler(c *gin.Context) {
	devdbid_ := c.Param("devdbid")
	if devdbid_ == "" {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "devdbid error."})
		return
	}
	devdbid, err := strconv.Atoi(devdbid_)
	if err != nil {
		logger.Warning("remove device, devdbid must be digit.")
		c.JSON(http.StatusBadRequest, gin.H{"msg": "error devdbid"})
		return
	}
	udevdbid := uint(devdbid)

	var device Device
	query := gldb.Where("id=? and stat='VALID'", udevdbid).First(&device)
	if query.Error != nil {
		logger.Warningf("cannot found special device. %d", udevdbid)
		c.AbortWithStatus(http.StatusNotFound)
		return
	}
	device.Stat = "DELETED"
	gldb.Save(&device)
	gldb.Delete(&device)
	c.JSON(http.StatusOK, gin.H{"msg": "OK"})
}

func PushMsgPlan(devdbid int, data string, c *gin.Context) {
	msgid, err := DbNewPushMessage(devdbid, []byte(data), "PLANING")
	if err != nil {
		logger.Warningf("DbNewMessage %s fail, %s", devdbid, err)
		c.JSON(http.StatusInternalServerError, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"msg": "OK", "msgid": msgid})
}

func PushMsgDirect(devdbid int, data string, c *gin.Context) {
	devinfo, err := DbDeviceDetail(uint(devdbid))
	if err != nil {
		logger.Warning("query device detail info failed ", err)
		c.JSON(http.StatusInternalServerError, gin.H{"msg": "query device detail failed."})
		return
	}
	if devinfo.PeerAddr == "" {
		logger.Warning("device detail info contain empty peer addr.")
		c.JSON(http.StatusBadRequest, gin.H{"msg": "devinfo empty peer addr"})
		return
	}
	addr, err := net.ResolveUDPAddr(protocol, devinfo.PeerAddr)
	if err != nil {
		logger.Warningf("resolve addr failed %s", devinfo.PeerAddr)
		c.JSON(http.StatusBadRequest, gin.H{"msg": "addr resolve failed."})
		return
	}
	logger.Infof("get last addr %s", addr.String())

	token, err := DbFetchDevLastToken(devinfo.Devdbid)
	if err != nil {
		logger.Warning("find last token failed.")
		c.JSON(http.StatusBadGateway, gin.H{"msg": "cannot found valid session."})
		return
	}

	err = PushCoapToAddr(data, addr, token)
	if err != nil {
		logger.Warningf("push message to %s failed, %s", addr.String(), err)
		c.JSON(http.StatusInternalServerError, gin.H{"msg": "push direct failed."})
		return
	}

	msgid, err := DbNewPushMessage(devdbid, []byte(data), "SENT")
	if err != nil {
		logger.Warningf("DbNewMessage %s fail, %s", devdbid, err)
		c.JSON(http.StatusInternalServerError, err)
		return
	}

	c.JSON(http.StatusOK, gin.H{"msg": "message push direct succ.", "msgid": msgid})
}

func PushMessagesHandler(c *gin.Context) {
	user := c.MustGet(JWT_KEY_USER).(User)

	var jsonreq struct {
		DevDbid int    `json:"devdbid" binding:"required"`
		Soon    string `json:"soon" binding:"required"`
		Data    string `json:"data" binding:"required"`
	}
	err := c.BindJSON(&jsonreq)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "bad request."})
		return
	}

	var device Device
	query := gldb.Model(&Device{}).Joins("join apps on devices.app_id = apps.id")
	query = query.Where("apps.user_id = ?", user.ID)
	query = query.Where("apps.stat='VALID' and devices.stat='VALID'")
	query = query.Where("devices.id=?", jsonreq.DevDbid).First(&device)
	if query.Error != nil {
		logger.Warningf("query devices failed.")
		c.AbortWithStatus(http.StatusNotFound)
		return
	}
	if jsonreq.Soon == "true" {
		PushMsgDirect(jsonreq.DevDbid, jsonreq.Data, c)
	} else {
		PushMsgPlan(jsonreq.DevDbid, jsonreq.Data, c)
	}
	return
}

func DevDetailHandler(c *gin.Context) {

}

func NotificationHandler(c *gin.Context) {
	email := c.Query("email")
	token := c.Query("access_token")
	if email == "" || token == "" {
		logger.Warningf("email or token empty, aborted.")
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	user, err := QueryUserFromEmailToken(email, token)
	if err != nil {
		logger.Warningf("query user failed. %s %s", email, token)
		c.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	v1wsupgrader := websocket.Upgrader{}
	conn, err := v1wsupgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		logger.Warning("upgrade to websocket failed.")
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}
	logger.Infof("recv user %d websocket connection", user.ID)
	wsChan := WsConnect{make(chan []byte), make(chan []byte)}
	wsclient_pool.AddUsersConn(user.ID, conn, wsChan)

	defer func() {
		conn.Close()
		wsclient_pool.RemoveUsersConn(user.ID, conn)
	}()

	go WebsocketProcHandler(conn, wsChan.ChIn)

	disconnected := false
	for {
		if disconnected {
			break
		}

		select {
		case v, ok := <-wsChan.ChIn:
			if !ok {
				logger.Warning("websocket rpc connection disconnected.")
				disconnected = true
				break
			}
			logger.Infof("data in... %s", string(v))
		case c := <-wsChan.ChOut:
			conn.WriteMessage(websocket.TextMessage, c)
		}
	}
	logger.Info("ws handler completed.")
}

func CheckRegistHandler(c *gin.Context) {
	//
}

func portalapiInitialize(router *gin.Engine) {
	router.GET(GAVATAR_PROXY_URL+":avatar/", AvatarProxyHandler)
	router.GET("/check_reg/", CheckRegistHandler)
	api_v1 := router.Group("/api/v1")
	{
		api_v1.POST("/login/", LoginHandler)
		api_v1.POST("/register/", RegisterHandler)
	}
	api_auth_v1 := router.Group("/api/v1")
	api_auth_v1.Use(jwt_middleware())
	{
		api_auth_v1.POST("/logout/", LogoutHandler)
		api_auth_v1.GET("/apps/", AppsListHandler)
		api_auth_v1.POST("/apps/", AddAppHandler)
		api_auth_v1.DELETE("/apps/:appid/", RemoveAppHandler)
		api_auth_v1.GET("/general/", GeneralHandler)
		api_auth_v1.GET("/devices/", DevicesListHandler)
		api_auth_v1.POST("/devices/", DevicesAddHandler)
		api_auth_v1.DELETE("/devices/:devdbid/", RemoveDeviceHandler)
		api_auth_v1.GET("/devices/:devdbid/", DevDetailHandler)
		api_auth_v1.GET("/sessions/", SessionsListHandler)
		api_auth_v1.GET("/gavatar/", UserIcoHandler)
		api_auth_v1.POST("/pushmessages/", PushMessagesHandler)
	}
	wsapi_v1 := router.Group("/api/v1")
	wsapi_v1.GET("/notification/", NotificationHandler)
}
